package net.qiqbframework.config;


import net.qiqbframework.commandhandling.CommandBus;
import net.qiqbframework.commandhandling.gateway.CommandGateway;
import net.qiqbframework.common.security.AuthUserProvider;
import net.qiqbframework.eventhandling.EventBus;
import net.qiqbframework.eventhandling.distributing.EventDistributor;
import net.qiqbframework.eventhandling.gateway.EventGateway;
import net.qiqbframework.eventsourcing.EventStorageEngine;
import net.qiqbframework.lifecycle.LifecycleHandler;
import net.qiqbframework.lifecycle.LifecycleHandlerRegister;
import net.qiqbframework.loadhanding.AggregateLoadBus;
import net.qiqbframework.messaging.Message;
import net.qiqbframework.messaging.annotation.HandlerParameterResolverFactory;
import net.qiqbframework.messaging.annotation.MessageHandlingMemberFactory;
import net.qiqbframework.messaging.correlation.CorrelationDataProvider;
import net.qiqbframework.messaging.monitoring.MessageMonitor;
import net.qiqbframework.modelling.domain.AggregateFactory;
import net.qiqbframework.modelling.repository.Repository;
import net.qiqbframework.persisthanding.AggregatePersistBus;
import net.qiqbframework.serialization.Serializer;
import net.qiqbframework.tracing.SpanFactory;
import org.javers.core.Javers;
import org.javers.core.JaversBuilder;

import javax.annotation.Nonnull;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * 描述组件全局配置的界面。它提供对配置的组件（如命令总线和事件总线）的访问。
 * 请注意，可能需要启动配置中的某些组件。因此，在使用此配置提供的任何组件之前，请确保已调用该 start() 组件
 */
public interface Configuration extends LifecycleHandlerRegister {

    default EventBus eventBus() {
        return getComponent(EventBus.class);
    }

    default EventDistributor eventDistributor() {
        return getComponent(EventDistributor.class);
    }

    /**
     * 返回此配置的生命周期注册表。通常，这只是配置本身的一个适配器，用于注册单个处理程序方法
     */
    default LifecycleHandlerRegister lifecycleRegistry() {
        return new LifecycleHandlerRegister() {
            @Override
            public void onStart(int phase, @Nonnull LifecycleHandler action) {
                Configuration.this.onStart(phase, action::handle);
            }

            @Override
            public void onShutdown(int phase, @Nonnull LifecycleHandler action) {
                Configuration.this.onShutdown(phase, action::handle);
            }
        };
    }

    /**
     * Finds all configuration modules of given {@code moduleType} within this configuration.
     *
     * @param moduleType The type of the configuration module
     * @param <T>        The type of the configuration module
     * @return configuration modules of {@code moduleType} defined in this configuration
     */
    @SuppressWarnings("unchecked")
    default <T extends ModuleConfiguration> List<T> findModules(@Nonnull Class<T> moduleType) {
        return getModules().stream()
                .filter(m -> m.isType(moduleType))
                .map(m -> (T) m.unwrap())
                .collect(Collectors.toList());
    }

    /**
     * Returns the Command Bus defined in this Configuration. Note that this Configuration should be started (see
     * {@link #start()}) before sending Commands over the Command Bus.
     *
     * @return the CommandBus defined in this configuration
     */
    default CommandBus commandBus() {
        return getComponent(CommandBus.class);
    }

    default AggregateLoadBus loadBus() {
        return getComponent(AggregateLoadBus.class);
    }

    default AggregatePersistBus persistBus() {
        return getComponent(AggregatePersistBus.class);
    }

    default EventStorageEngine eventStorageEngine() {
        return getComponent(EventStorageEngine.class);
    }


    default CommandGateway commandGateway() {
        return getComponent(CommandGateway.class);
    }


    default JaversBuilder javersBuilder() {
        return getComponent(JaversBuilder.class);
    }

    default Javers javers() {
        final JaversBuilder javersBuilder = javersBuilder();
        return getComponent(Javers.class, javersBuilder::build);
    }

    default AuthUserProvider authUserProvider() {
        return getComponent(AuthUserProvider.class);
    }

    default EventProcessingConfiguration eventProcessingConfiguration() throws ConfigurationException {
        List<EventProcessingConfiguration> eventProcessingModules =
                getModules().stream()
                        .filter(module -> module.isType(EventProcessingConfiguration.class))
                        .map(module -> (EventProcessingConfiguration) module.unwrap())
                        .collect(Collectors.toList());
        switch (eventProcessingModules.size()) {
            case 0:
                return null;
            case 1:
                return eventProcessingModules.get(0);
            default:
                throw new ConfigurationException(
                        "There are several EventProcessingConfigurations defined. Use findModules(Class<T>) method instead.");
        }
    }


    /**
     * Returns the Event Gateway defined in this Configuration.
     *
     * @return the EventGateway defined in this configuration
     */
    default EventGateway eventGateway() {
        return getComponent(EventGateway.class);
    }

    /**
     * Returns the Tags Configuration defined in this Configuration.
     *
     * @return the Tags Configuration defined in this Configuration
     */
    default TagsConfiguration tags() {
        return getComponent(TagsConfiguration.class);
    }

    /**
     * Returns the {@link SpanFactory} defined in this configuration.
     *
     * @return the {@link SpanFactory} defined in this configuration.
     */
    default SpanFactory spanFactory() {
        return getComponent(SpanFactory.class);
    }

    /**
     * Returns the {@link AggregateConfiguration} for the given {@code aggregateType}.
     *
     * @param aggregateType the aggregate type to find the {@link AggregateConfiguration} for
     * @param <A>           the aggregate type
     * @return the {@link AggregateConfiguration} for the given {@code aggregateType}
     */
    default <A> AggregateConfiguration<A> aggregateConfiguration(@Nonnull Class<A> aggregateType) {
        // noinspection unchecked
        return findModules(AggregateConfiguration.class)
                .stream()
                .filter(aggregateConfig -> aggregateConfig.aggregateType().isAssignableFrom(aggregateType))
                .findFirst()
                .map(moduleConfig -> (AggregateConfiguration<A>) moduleConfig)
                .orElseThrow(() -> new IllegalArgumentException(
                        "Aggregate " + aggregateType.getSimpleName() + " has not been configured"
                ));
    }

    /**
     * Returns the {@link Repository} configured for the given {@code aggregateType}.
     *
     * @param aggregateType the aggregate type to find the {@link Repository} for
     * @param <A>           the aggregate type
     * @return the {@link Repository} from which aggregates of the given {@code aggregateType} can be loaded
     */
    default <A> Repository<A> repository(@Nonnull Class<A> aggregateType) {
        return aggregateConfiguration(aggregateType).repository();
    }

    /**
     * Returns the {@link AggregateFactory} configured for the given {@code aggregateType}.
     *
     * @param aggregateType the aggregate type to find the {@link AggregateFactory} for
     * @param <A>           the aggregate type
     * @return the {@link AggregateFactory} which constructs aggregate of the given {@code aggregateType}
     */
    default <A> AggregateFactory<A> aggregateFactory(@Nonnull Class<A> aggregateType) {
        return aggregateConfiguration(aggregateType).aggregateFactory();
    }

    /**
     * Returns the Component declared under the given {@code componentType}, typically the interface the component
     * implements.
     *
     * @param componentType The type of component
     * @param <T>           The type of component
     * @return the component registered for the given type, or {@code null} if no such component exists
     */
    default <T> T getComponent(@Nonnull Class<T> componentType) {
        return getComponent(componentType, () -> null);
    }

    /**
     * Returns the Component declared under the given {@code componentType}, typically the interface the component
     * implements, reverting to the given {@code defaultImpl} if no such component is defined.
     * <p>
     * When no component was previously registered, the default is then configured as the component for the given type.
     *
     * @param componentType The type of component
     * @param defaultImpl   The supplier of the default to return if no component was registered
     * @param <T>           The type of component
     * @return the component registered for the given type, or the value returned by the {@code defaultImpl} supplier,
     * if no component was registered
     */
    <T> T getComponent(@Nonnull Class<T> componentType, @Nonnull Supplier<T> defaultImpl);

    /**
     * Returns the message monitor configured for a component of given {@code componentType} and {@code componentName}.
     *
     * @param componentType The type of component to return the monitor for
     * @param componentName The name of the component
     * @param <M>           The type of message the monitor can deal with
     * @return The monitor to be used for the described component
     */
    <M extends Message<?>> MessageMonitor<? super M> messageMonitor(@Nonnull Class<?> componentType,
                                                                    @Nonnull String componentName);

    /**
     * Returns the serializer defined in this Configuration
     *
     * @return the serializer defined in this Configuration
     */
    default Serializer serializer() {
        return getComponent(Serializer.class);
    }


    Serializer eventSerializer();

    Serializer messageSerializer();

    Serializer aggregateSerializer();

    /**
     * Starts this configuration. All components defined in this Configuration will be started.
     */
    void start();

    /**
     * Shuts down the components defined in this Configuration
     */
    void shutdown();

    /**
     * Returns the Correlation Data Providers defined in this Configuration.
     *
     * @return the Correlation Data Providers defined in this Configuration
     */
    List<CorrelationDataProvider> correlationDataProviders();

    /**
     * Returns the Parameter Resolver Factory defined in this Configuration
     *
     * @return the Parameter Resolver Factory defined in this Configuration
     */
    default HandlerParameterResolverFactory parameterResolverFactory() {
        return getComponent(HandlerParameterResolverFactory.class);
    }

    /**
     * Returns the Handler Definition defined in this Configuration for the given {@code inspectedType}.
     *
     * @param inspectedType The class to being inspected for handlers
     * @return the Handler Definition defined in this Configuration
     */
    MessageHandlingMemberFactory handlerDefinition(Class<?> inspectedType);


    /**
     * Returns all modules that have been registered with this Configuration.
     *
     * @return all modules that have been registered with this Configuration
     */
    List<ModuleConfiguration> getModules();

}
